﻿using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;

namespace Digitalmes.WebApi.Infrastructure.Jwt;

/// <summary>
/// JWT 验证管理对象。
/// </summary>
public sealed class JwtAuthManager(IOptions<JwtTokenConfig> jwtTokenConfig) : IJwtAuthManager
{
    // can store in a database or a distributed cache
    private readonly byte[] _secret = Encoding.UTF8.GetBytes(jwtTokenConfig.Value.SecretKey);

    public string GenerateToken(Claim[] claims, DateTime now, TimeSpan expires)
    {
        return CreateToken(claims, now, expires);
    }

    public string Refresh(string refreshToken, DateTime now, TimeSpan expires)
    {
        var (principal, jwtToken) = DecodeJwtToken(refreshToken);
        if (jwtToken == null || !jwtToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256Signature))
        {
            throw new SecurityTokenException("Invalid token");
        }

        return CreateToken(principal.Claims.ToArray(), now, expires);
    }

    public (ClaimsPrincipal, JwtSecurityToken?) DecodeJwtToken(string token)
    {
        if (string.IsNullOrWhiteSpace(token))
        {
            throw new SecurityTokenException("Invalid token");
        }

        var principal = new JwtSecurityTokenHandler()
            .ValidateToken(token,
                new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidIssuer = jwtTokenConfig.Value.Issuer,
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(_secret),
                    ValidAudience = jwtTokenConfig.Value.Audience,
                    ValidateAudience = true,
                    ValidateLifetime = true,
                    ClockSkew = TimeSpan.FromMinutes(2), // 允许容忍的时间差
                },
                out var validatedToken);
        return (principal, validatedToken as JwtSecurityToken);
    }

    private string CreateToken(Claim[] claims, DateTime now, TimeSpan expires)
    {
        var jwtToken = new JwtSecurityToken(
            issuer: jwtTokenConfig.Value.Issuer,
            audience: jwtTokenConfig.Value.Audience,
            claims: claims,
            expires: now.Add(expires),
            signingCredentials: new SigningCredentials(new SymmetricSecurityKey(_secret), SecurityAlgorithms.HmacSha256Signature));
        return new JwtSecurityTokenHandler().WriteToken(jwtToken);
    }
}
